﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Microsoft.Win32;
using System.Security;
using System.IO;
using Citrix.XenApp.Commands;
using Citrix.XenApp.Sdk;
using System.Management.Automation.Runspaces;
using System.Reflection;
using Citrix.XaAdminMobile.Util;
using Citrix.Management.Automation;
using System.Management.Automation;

namespace Citrix.XaAdminMobile.Model
{
    /// <summary>
    /// Manages the storage and retrieval of XenApp server configuration from persistent
    /// storage. This app has to talk to a XenApp server to perform it's admin tasks. By default
    /// it will use localhost, however you can configure it to point at a different server.
    /// </summary>
    internal class ServerConfigModel : IServerConfigModel
    {
        /// <summary>
        /// The location in the registry that we store config.
        /// </summary>
        private const string RegPath = @"Software\Citrix\MobileXenAppAdmin";

        /// <summary>
        /// The reg key name of the server the user wants to use (type REG_SZ).
        /// </summary>
        private const string ServerKey = @"ServerName";

        /// <summary>
        /// The reg key containing the previously used servers (REG_MULTI_SZ).
        /// </summary>
        private const string ServerHistoryKey = "ServerHistory";

        /// <summary>
        /// The default server we use.
        /// </summary>
        private const string LocalHost = "localhost";

        /// <summary>
        /// The currently configured server.
        /// </summary>
        private string currentServer;

        /// <summary>
        /// The set of known servers in our history. Never includes localhost.
        /// </summary>
        private List<string> serverHistory;

        /// <summary>
        /// Ref back to the base/parent model object.
        /// </summary>
        private readonly DefaultModel modelBase;

        /// <summary>
        /// Ctor
        /// </summary>
        /// <param name="modelRoot">The root model object.</param>
        /// <exception cref="ArgumentNullException">Thrown if any arg is null.</exception>
        public ServerConfigModel(DefaultModel modelRoot)
        {
            if (null == modelRoot)
            {
                throw new ArgumentNullException("modelRoot");
            }

            this.modelBase = modelRoot;
            this.serverHistory = new List<string>();
            this.currentServer = LocalHost;
            Load();
        }

        /// <summary>
        /// Saves the supplied historical list of server names to persistent storage. Null/empty
        /// server names are ignored and any localhost entries are ignored.
        /// </summary>
        /// <param name="servers">The list of server names to save.</param>
        public void SetServerHistory(List<string> servers)
        {
            if (null == servers)
            {
                servers = new List<string>();
            }

            // Trim empty entries and localhost.
            var filtered = (from s in servers
                            where !string.IsNullOrEmpty(s) && !string.Equals(s, LocalHost, StringComparison.OrdinalIgnoreCase)
                            select s).ToArray();
            try
            {
                using (var key = Registry.CurrentUser.CreateSubKey(RegPath))
                {
                    if (null != key)
                    {
                        key.SetValue(ServerHistoryKey, filtered, RegistryValueKind.MultiString);
                    }
                }

                this.serverHistory = filtered.ToList();
            }
            catch (ArgumentNullException)
            {
            }
            catch (ObjectDisposedException)
            {
            }
            catch (IOException)
            {
            }
            catch (SecurityException)
            {
            }
            catch (UnauthorizedAccessException)
            {
            }
        }

        /// <summary>
        /// Sets and saves to persistent storage the name of the XenApp server this app should
        /// connect to for performing its admin tasks.
        /// 
        /// If you set a null/empty name then we set the server name to localhost instead.
        /// </summary>
        /// <param name="serverName">XenApp server name.</param>
        public void SetServerName(string serverName)
        {
            if (string.IsNullOrEmpty(serverName))
            {
                serverName = LocalHost;
            }

            try
            {
                using (var key = Registry.CurrentUser.CreateSubKey(RegPath))
                {
                    if (null != key)
                    {
                        key.SetValue(ServerKey, serverName, RegistryValueKind.String);
                    }
                }
                this.currentServer = serverName;
            }
            catch (ArgumentNullException)
            {
            }
            catch (ObjectDisposedException)
            {
            }
            catch (IOException)
            {
            }
            catch (SecurityException)
            {
            }
            catch (UnauthorizedAccessException)
            {
            }
        }

        /// <summary>
        /// The name of the currently configured server.
        /// </summary>
        public string ServerName
        {
            get { return this.currentServer; }
        }

        /// <summary>
        /// Returns the history of previously used XenApp servers.
        /// </summary>
        public List<string> ServerHistory
        {
            get
            {
                return this.serverHistory;
            }
        }

        /// <summary>
        /// Makes an XenApp admin query to the supplied server to see if it is successful in
        /// order to determine whether it is a valid server to use.
        /// </summary>
        /// <param name="serverName">The server name to test.</param>
        /// <param name="error">On an error this will contain an error message.</param>
        /// <returns>True if successful, false if not.</returns>
        public bool TestServerName(string serverName, out string error)
        {
            bool valid = false;
            error = null;
            try
            {
                if (!string.IsNullOrEmpty(serverName))
                {
                    // Test the server by trying to connect to it and query it for itself.
                    var result = this.modelBase.RunspacePool.ExecuteCommand(
                        new GetXAServerByName() { ComputerName = serverName, ServerName = new[] { serverName } });
                    valid = true;
                }
            }
            catch (InvalidRunspacePoolStateException ex)
            {
                FTrace.Except(MethodBase.GetCurrentMethod(), ex);
                error = ex.Message;
            }
            catch (CmdletInvocationException ex)
            {
                FTrace.Except(MethodBase.GetCurrentMethod(), ex);
                error = ex.Message;
            }
            catch (Exception ex)
            {
                FTrace.Except(MethodBase.GetCurrentMethod(), ex);
                throw;
            }

            return valid;
        }

        /// <summary>
        /// Loads the currently configured server name and server history from the registry.
        /// </summary>
        private void Load()
        {
            bool success = false;
            try
            {
                using (var key = Registry.CurrentUser.OpenSubKey(RegPath))
                {
                    if (null != key)
                    {
                        if (key.GetValueKind(ServerHistoryKey) == RegistryValueKind.MultiString)
                        {
                            var servers = key.GetValue(ServerHistoryKey) as string[];
                            if (null != servers)
                            {
                                this.serverHistory = (from s in servers
                                                      where !string.IsNullOrEmpty(s) && !string.Equals(s, LocalHost, StringComparison.OrdinalIgnoreCase)
                                                      select s).ToList();
                            }
                        }

                        if (key.GetValueKind(ServerKey) == RegistryValueKind.String)
                        {
                            this.currentServer = key.GetValue(ServerKey) as string;

                            if (!string.IsNullOrEmpty(this.currentServer))
                            {
                                success = true;

                                // Double check that the currently selected server is also in
                                // the server history and add it (as long as it is not
                                // localhost).
                                if (!string.Equals(this.currentServer, LocalHost, StringComparison.OrdinalIgnoreCase) &&
                                    !this.serverHistory.Contains(this.currentServer))
                                {
                                    this.serverHistory.Insert(0, this.currentServer);
                                }
                            }
                        }

                    }
                }
            }
            catch (ArgumentNullException)
            {
            }
            catch (ObjectDisposedException)
            {
            }
            catch (IOException)
            {
            }
            catch (SecurityException)
            {
            }
            catch (UnauthorizedAccessException)
            {
            }

            // Default to localhost for the current server name if there is any error.
            if (!success)
            {
                this.currentServer = LocalHost;
            }
        }
    }
}
